/****************************************\
|* MFZPSP LLE PSP Emulator              *|
|* Copyright (c) 2008 Mauro Frischherz  *|
|* See License.txt for details          *|
\****************************************/

#include "loader.h"
#include "memory.h"
#include "cpu.h"
#include "../tools/log.h"
#include <stdio.h>
#include <malloc.h>

/*							*/
/* ELF Defines and Structs	*/
/*							*/

// ELF Ident Indexes
#define EI_MAG0			0
#define EI_MAG1			1
#define EI_MAG2			2
#define EI_MAG3			3
#define EI_CLASS		4
#define EI_DATA			5
#define EI_VERSION		6
#define EI_PAD			7
#define EI_NIDENT		16

// ELF Types
#define ET_NONE			0
#define ET_REL			1
#define ET_EXEC			2
#define ET_DYN			3
#define ET_CORE			4
#define ET_LOPROC		0xFF00
#define ET_HIPROC		0xFFFF
#define ET_PRX			0xFFA0

// ELF Machine -> MIPS
#define EM_NONE			0
#define EM_MIPS			8

// ELF Classes
#define ELFCLASSNONE	0
#define ELFCLASS32		1
#define ELFCLASS64		2

// ELF Data Encoding
#define ELFDATANONE		0
#define ELFDATA2LSB		1
#define ELFDATA2MSB		2

// ELF Section Types
#define SHT_NULL		0
#define SHT_PROGBITS	1
#define SHT_SYMTAB		2
#define SHT_STRTAB		3
#define SHT_RELA		4
#define SHT_HASH		5
#define SHT_DYNAMIC		6
#define SHT_NOTE		7
#define SHT_NOBITS		8
#define SHT_REL			9
#define SHT_SHLIB		10
#define SHT_DYNSYM		11
#define SHT_LOPROC		0x70000000
#define SHT_HIPROC		0x7FFFFFFF
#define SHT_LOUSER		0x80000000
#define SHT_HIUSER		0xFFFFFFFF

// ELF Section Attribute Flags
#define SHT_WRITE		0x1
#define SHF_ALLOC		0x2
#define SHF_EXECINSTR	0x4
#define SHR_MASKPROC	0xF0000000

// ELF Relocation Types
#define R_MIPS_NONE			0
#define R_MIPS_16			1
#define R_MIPS_32			2
#define R_MIPS_REL32		3
#define R_MIPS_26			4
#define R_MIPS_HI16			5
#define R_MIPS_LO16			6
#define R_MIPS_GPREL16		7
#define R_MIPS_LITERAL		8
#define R_MIPS_GOT16		9
#define R_MIPS_PC16			10
#define R_MIPS_CALL16		11
#define R_MIPS_GPREL32		12

// ELF Segment Types
#define PT_NULL			0
#define PT_LOAD			1
#define PT_DYNAMIC		2
#define PT_INTERP		3
#define PT_NOTE			4
#define PT_SHLIB		5
#define PT_PHDR			6
#define PT_LOPROC		0x70000000
#define PT_HIPROC		0x7FFFFFFF

// ELF Magic Number 0x7F 'E' 'L' 'F'
#define ELFMAGNUM		0x464C457F

// Macros
#define ELF32_R_ADDR_BASE(i) 	(((i)> >16) & 0xFF)
#define ELF32_R_OFS_BASE(i) 	(((i)> >8) & 0xFF)
#define ELF32_R_TYPE(i)			(i & 0xFF)

// ELF Header
typedef struct {
	// Header
	ubyte ident[EI_NIDENT];
	ushort type;
	ushort machine;
	uint  version;
	uint  entry;
	uint  phoff;
	uint  shoff;
	uint  flags;
	ushort ehsize;
	ushort phentsize;
	ushort phnum;
	ushort shentsize;
	ushort shnum;
	ushort shstrndx;
} ELFFileInfo;

// ELF Section Header
typedef struct {
	char sctname[100];
	uint num;
	uint name;
	uint type;
	uint flags;
	uint addr;
	uint offset;
	uint size;
	uint link;
	uint info;
	uint addralign;
	uint entsize;
} ELFSectionHeader;

// ELF Symbol Table
typedef struct {
	uint name;
	uint value;
	uint size;
	ubyte info;
	ubyte other;
	short shndx;
} ELFSymbolTable;

// ELF Relocation Entry
typedef struct {
	uint offset;
	uint info;
} ELFRelocationEntry;

// ELF Relocation Entry Addend
typedef struct {
	uint offset;
	uint info;
	uint addend;
} ELFRelocationEntryAddend;

// ELF Program Header
typedef struct {
	uint type;
	uint offset;
	uint vaddr;
	uint paddr;
	uint filesz;
	uint memsz;
	uint flags;
	uint align;
} ELFProgramHeader;

/*							*/
/* PBP Defines and Structs	*/
/*							*/

// PBP Magic Number 0x00 'P' 'B' 'P'
#define PBPMAGNUM 		0x50425000

// PBP Header + FileInfo
typedef struct {
	// Header
	uint ident;
	uint version;
	uint offsetparam;
	uint offseticon0;
	uint offseticon1;
	uint offsetuknpng;
	uint offsetpic1;
	uint offsetsnd0;
	uint offsetpsp;
	uint offsetpsar;
	
	// FileInfo
	uint sizeparam;
	uint sizeicon0;
	uint sizeicon1;
	uint sizeuknpng;
	uint sizepic1;
	uint sizesnd0;
	uint sizepsp;
	uint sizepsar;
} PBPFileInfo;

bool PrepareFile();
bool DecodeFile();
bool WriteToMemory();
bool IsValidFile();

bool FileLoaded = false;
int  FileType = 0;
FILE* pFile = NULL;

PBPFileInfo PBPInfo;

ELFFileInfo ELFInfo;
ELFProgramHeader* ELFPrgHdr;
ELFSectionHeader* ELFSctHdr;

uint relocbase;
bool relocation;
uint entrypoint;

bool GetLoaded() { return FileLoaded; }
uint GetEntry() { return entrypoint; }

void ResetLoader()
{
	ResetMemory();
	
	FileLoaded = false;
	relocbase = 0;
	relocation = false;
	entrypoint = 0;
}


bool LoadAndDecodeFile(const char* path, int type)
{
	ResetLoader();
	FileType = type;
	
	if(pFile)
		fclose(pFile);
	
	char buffer[500];
	sprintf(buffer, "Opening: %s", path);
	printevent(buffer);
	
	pFile = fopen(path,"rb");
	
	if(pFile == NULL)
		return false;
	
	if(!PrepareFile())
	{
		fclose(pFile);
		printerror("Load cancelled");
		return false;
	}
	
	if(!IsValidFile())
	{
		fclose(pFile);
		printerror("Load cancelled");
		return false;
	}
	
	if(!DecodeFile())
	{
		fclose(pFile);
		printerror("Load cancelled");
		return false;
	}
	
	if(!WriteToMemory())
	{
		fclose(pFile);
		printerror("Load cancelled");
		return false;
	}
	
	FileLoaded = true;
	fclose(pFile);
	return true;
}

bool PrepareFile()
{
	switch(FileType)
	{
	case FILE_TYPE_PBP:
		{
			printevent("Preparing PBP-File");
			uint PBPHeader[10];
			fread(PBPHeader, sizeof(PBPHeader[0]), sizeof(PBPHeader)/sizeof(PBPHeader[0]), pFile);
			
			fseek(pFile, 0, SEEK_END);
			uint filesize = ftell(pFile);
			
			PBPInfo.ident 			= PBPHeader[0];
			PBPInfo.version 		= PBPHeader[1];
			PBPInfo.offsetparam 	= PBPHeader[2];
			PBPInfo.offseticon0 	= PBPHeader[3];
			PBPInfo.offseticon1 	= PBPHeader[4];
			PBPInfo.offsetuknpng  	= PBPHeader[5];
			PBPInfo.offsetpic1		= PBPHeader[6];
			PBPInfo.offsetsnd0		= PBPHeader[7];
			PBPInfo.offsetpsp		= PBPHeader[8];
			PBPInfo.offsetpsar		= PBPHeader[9];
			
			PBPInfo.sizeparam 		= PBPInfo.offseticon0 - PBPInfo.offsetparam;
			PBPInfo.sizeicon0 		= PBPInfo.offseticon1 - PBPInfo.offseticon0;
			PBPInfo.sizeuknpng 		= PBPInfo.offsetpic1 - PBPInfo.offsetuknpng;
			PBPInfo.sizepic1		= PBPInfo.offsetsnd0 - PBPInfo.offsetpic1;
			PBPInfo.sizesnd0		= PBPInfo.offsetpsp - PBPInfo.offsetsnd0;
			PBPInfo.sizepsp			= PBPInfo.offsetpsar - PBPInfo.offsetpsp;
			PBPInfo.sizepsar		= filesize - PBPInfo.offsetpsar;
			
			printevent("Successfully read PBP-Header");
		}
		break;
	case FILE_TYPE_ELF:
		{
			printevent("Preparing ELF-File");
			int i;
			ubyte ELFIdent[16] = { 0 };
			fseek(pFile, 0, SEEK_SET);
			fread(ELFIdent, sizeof(ELFIdent[0]), sizeof(ELFIdent)/sizeof(ELFIdent[0]), pFile);
			
			ushort ELFShortHdr[26] = { 0 };
			fseek(pFile, 0, SEEK_SET);
			fread(ELFShortHdr, sizeof(ELFShortHdr[0]), sizeof(ELFShortHdr)/sizeof(ELFShortHdr[0]), pFile);
			
			uint ELFWordHdr[13] = { 0 };
			fseek(pFile, 0, SEEK_SET);
			fread(ELFWordHdr, sizeof(ELFWordHdr[0]), sizeof(ELFWordHdr)/sizeof(ELFWordHdr[0]), pFile);
	
			for(i = 0; i < 16; i++)
			{
				ELFInfo.ident[i] 	= ELFIdent[i];
			}
			
			// Set Header
			ELFInfo.type		= ELFShortHdr[8];
			ELFInfo.machine		= ELFShortHdr[9];
			
			ELFInfo.version		= ELFWordHdr[5];
			ELFInfo.entry		= ELFWordHdr[6];
			ELFInfo.phoff		= ELFWordHdr[7];
			ELFInfo.shoff		= ELFWordHdr[8];
			ELFInfo.flags		= ELFWordHdr[9];
			
			ELFInfo.ehsize		= ELFShortHdr[20];
			ELFInfo.phentsize	= ELFShortHdr[21];
			ELFInfo.phnum		= ELFShortHdr[22];
			ELFInfo.shentsize	= ELFShortHdr[23];
			ELFInfo.shnum		= ELFShortHdr[24];
			ELFInfo.shstrndx	= ELFShortHdr[25];
			
			printevent("Successfully read ELF-Header");
			
			// Check if executable or prx
			switch(ELFInfo.type) {
				case ET_EXEC:
					printevent("ELF is an executable (type = 0x0002)");
					break;
				case ET_PRX:
					printevent("ELF is a PRX (type = 0xFFA0)");
					break;
				default:
					printerror("ELF is not compatible (type != EXEC or PRX)");
					return false;
					break;
			}
			
			// Check machine
			if(!ELFInfo.machine == EM_MIPS)
			{
				printerror("Not a MIPS ELF (machine != 0x0008");
				return false;
			}
			
			// Needs relocation if entry lower than 0x08000000
			if(ELFInfo.entry < 0x08000000)
				relocbase = 0x08900000;
		}
		break;
	default:
		{
			printerror("Unimplemented filetype!");
			return false;
		}
		break;
	}
	return true;
}

bool IsValidFile()
{
	switch(FileType)
	{
	case FILE_TYPE_PBP:
		{
			// PBP MagicNumber Check && ELF > 0 bytes
			if(!(PBPInfo.ident == PBPMAGNUM && PBPInfo.sizepsp > 0))
				return false;
		}
		break;
	case FILE_TYPE_ELF:
		{
			// ELF Magic Number Check
			if(!(ELFInfo.ident[EI_MAG0] == 0x7F &&
				 ELFInfo.ident[EI_MAG1] == 0x45 && 
				 ELFInfo.ident[EI_MAG2] == 0x4C && 
				 ELFInfo.ident[EI_MAG3] == 0x46) ||
				 ELFInfo.phnum == 0)
				return false;
		}
	}
	printevent("File-Check result: Valid");
	return true;
}

bool DecodeFile()
{
	switch(FileType)
	{
	case FILE_TYPE_PBP:
		{
			printevent("Unpacking PBP");
			/* Unpack PBP */
			
			FILE* pFileTarget;
			
			/* PARAM.SFO */
			if(PBPInfo.sizeparam > 0)
			{
				printevent("--> PARAM.SFO");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("PARAM.SFO", "wb");
				for(steps = 0; steps < PBPInfo.sizeparam; steps += 4)
				{
					fseek(pFile, PBPInfo.offsetparam + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* ICON0.PNG */
			if(PBPInfo.sizeicon0 > 0)
			{
				printevent("--> ICON0.PNG");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("ICON0.PNG", "wb");
				for(steps = 0; steps < PBPInfo.sizeicon0; steps += 4)
				{
					fseek(pFile, PBPInfo.offseticon0 + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* ICON1.PMF */
			if(PBPInfo.sizeicon1 > 0)
			{
				printevent("--> ICON1.PMF");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("ICON1.PMF", "wb");
				for(steps = 0; steps < PBPInfo.sizeicon1; steps += 4)
				{
					fseek(pFile, PBPInfo.offseticon1 + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* UNKOWNPNG */
			if(PBPInfo.sizeuknpng > 0)
			{
				printevent("--> UNKNOWN.PNG");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("UNKNOWN.PNG", "wb");
				for(steps = 0; steps < PBPInfo.sizeuknpng; steps += 4)
				{
					fseek(pFile, PBPInfo.offsetuknpng + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* PIC1.PNG */
			if(PBPInfo.sizepic1 > 0)
			{
				printevent("--> PIC1.PNG");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("PIC1.PNG", "wb");
				for(steps = 0; steps < PBPInfo.sizepic1; steps += 4)
				{
					fseek(pFile, PBPInfo.offsetpic1 + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* SND0.AT3 */
			if(PBPInfo.sizesnd0 > 0)
			{
				printevent("--> SND0.AT3");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("SND0.AT3", "wb");
				for(steps = 0; steps < PBPInfo.sizesnd0; steps += 4)
				{
					fseek(pFile, PBPInfo.offsetsnd0 + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* PSP.ELF */
			if(PBPInfo.sizepsp > 0)
			{
				printevent("--> PSP.ELF");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("PSP.ELF", "wb");
				
				for(steps = 0; steps < PBPInfo.sizepsp; steps += 4)
				{
					fseek(pFile, PBPInfo.offsetpsp + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			/* PSAR.DATA */
			if(PBPInfo.sizepsar > 0)
			{
				printevent("--> PSAR.DATA");
				uint steps;
				uint Buffer;
				pFileTarget = fopen("PSAR.DATA", "wb");
				
				for(steps = 0; steps < PBPInfo.sizepsar; steps += 4)
				{
					fseek(pFile, PBPInfo.offsetpsar + steps, SEEK_SET);
					fread(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer), pFile);
					fwrite(&Buffer, sizeof(Buffer), sizeof(Buffer)/sizeof(Buffer),pFileTarget);
				}
				fclose(pFileTarget);
			}
			
			printevent("Unpacking completed");
			
			// Switching to ELF processing
			printevent("Opening unpacked ELF-File");
			if(!LoadAndDecodeFile(".\\PSP.ELF", FILE_TYPE_ELF))
			{
				printerror("Failed to process ELF-File");
				return false;
			}
			FileType = FILE_TYPE_PBP;
		}
		break;
	case FILE_TYPE_ELF:
		{
			printevent("Reading ELF program and section headers");
			
			// Read Program Header
			uint PHBuffer[8];
			
			int i;
			if(ELFInfo.phoff != 0)
			{
				// Resize Array
				ELFPrgHdr = (ELFProgramHeader*) realloc(0, ELFInfo.phnum * sizeof(ELFProgramHeader));
				
				for(i = 0; i < ELFInfo.phnum; i++)
				{
					fseek(pFile, ELFInfo.phoff + (i * ELFInfo.phentsize), SEEK_SET); // Thanks to anonymous person from blog
					fread(PHBuffer, sizeof(PHBuffer[0]), sizeof(PHBuffer) / sizeof(PHBuffer[0]), pFile);
					
					ELFPrgHdr[i].type   = PHBuffer[0];
					ELFPrgHdr[i].offset = PHBuffer[1];
					ELFPrgHdr[i].vaddr  = PHBuffer[2];
					ELFPrgHdr[i].paddr  = PHBuffer[3];
					ELFPrgHdr[i].filesz = PHBuffer[4];
					ELFPrgHdr[i].memsz  = PHBuffer[5];
					ELFPrgHdr[i].flags  = PHBuffer[6];
					ELFPrgHdr[i].align  = PHBuffer[7];
				}
			}
			printevent("Read program header(s)");
			
			
			uint SHBuffer[10];
			if(ELFInfo.shoff != 0)
			{
				// Resize Array
				ELFSctHdr = (ELFSectionHeader*) realloc(0, ELFInfo.shnum * sizeof(ELFSectionHeader));
				
				// Read Section Headers
				for(i = 0; i < ELFInfo.shnum; i++)
				{
					fseek(pFile, ELFInfo.shoff + (i * ELFInfo.shentsize), SEEK_SET);
					fread(SHBuffer, sizeof(SHBuffer[0]), sizeof(SHBuffer) / sizeof(SHBuffer[0]), pFile);
					
					ELFSctHdr[i].num		= i;
					ELFSctHdr[i].name   	= SHBuffer[0];
					ELFSctHdr[i].type 		= SHBuffer[1];
					ELFSctHdr[i].flags  	= SHBuffer[2];
					ELFSctHdr[i].addr  		= SHBuffer[3];
					ELFSctHdr[i].offset 	= SHBuffer[4];
					ELFSctHdr[i].size  		= SHBuffer[5];
					ELFSctHdr[i].link  		= SHBuffer[6];
					ELFSctHdr[i].info  		= SHBuffer[7];
					ELFSctHdr[i].addralign 	= SHBuffer[8];
					ELFSctHdr[i].entsize 	= SHBuffer[9];
				}
				
				printevent("Read section header(s)");
				printevent("Reading section names from .shstrtab");
				if(ELFSctHdr[ELFInfo.shstrndx].type == SHT_STRTAB)
				{
					char sctbuffer[100];
					printevent("--> Nr |  Address   |             Name             |       Size      ");
					printevent("-->----|------------|------------------------------|-----------------");
					// Name Sections
					for(i = 0; i < ELFInfo.shnum; i++)
					{
						if(ELFSctHdr[i].name > 0)
						{
							fseek(pFile, ELFSctHdr[ELFInfo.shstrndx].offset + ELFSctHdr[i].name, SEEK_SET);
							fread(ELFSctHdr[i].sctname, sizeof(ELFSctHdr[i].sctname[0]), sizeof(ELFSctHdr[i].sctname)/sizeof(ELFSctHdr[i].sctname[0]), pFile);
							sprintf(sctbuffer, "--> %02i | 0x%08X | %-28s | %8i bytes", i+1, ELFSctHdr[i].offset, ELFSctHdr[i].sctname, ELFSctHdr[i].size);
							printevent(sctbuffer);
						}
						else
						{
							sprintf(sctbuffer, "--> %02i | 0x%08X | %-28s | %8i bytes",  i+1, ELFSctHdr[i].offset, "Unnamed", ELFSctHdr[i].size);
							printevent(sctbuffer);
						}
					}
					printevent("-->----+------------+------------------------------+-----------------");
				}
			}
			printevent("Successfully processed ELF-File");
		}
	}
	return true;
}

bool WriteToMemory()
{
	switch(FileType)
	{
	case FILE_TYPE_PBP:
		printevent("Successfully loaded PBP-File");
		break;
	case FILE_TYPE_ELF:
		{
			// Write sections to memory
			uint i;
			for(i = 0; i < ELFInfo.shnum; i++)
			{
				if(ELFSctHdr[i].size > 0 && ((ELFSctHdr[i].flags & SHF_ALLOC) || (ELFSctHdr[i].flags & SHF_EXECINSTR)))
				{
					switch(ELFSctHdr[i].type) {
						case SHT_PROGBITS:
						{
							ubyte sctbuffer[4];
							// Read section
							//sctbuffer = (ubyte*) calloc(ELFSctHdr[i].size, sizeof(ubyte));
							int j;
							for(j = 0; j < ELFSctHdr[i].size; j += 4)
							{
								fseek(pFile, ELFSctHdr[i].offset + j, SEEK_SET);
								fread(sctbuffer, sizeof(sctbuffer[0]), sizeof(sctbuffer)/sizeof(sctbuffer[0]), pFile);
								WriteLarge(ELFSctHdr[i].addr + relocbase + j, sctbuffer, 4);
							}
							//free(sctbuffer);
						}
						break;
						case SHT_NOBITS:
						{							
							// Zero memory
							ZeroMem(ELFSctHdr[i].addr + relocbase, ELFSctHdr[i].size);
							
						}
						break;
					}
				}
			}
			printevent("Written ELF sections to memory");
			entrypoint = ELFInfo.entry + relocbase;
			SetEntry(entrypoint);
			free(ELFPrgHdr);
			free(ELFSctHdr);
		}
		break;
	}
	
	printevent("File written to memory");
	return true;
}


